package UI;

import Controller.Midlet;
import FileHandling.FileOps;
import javax.microedition.lcdui.*;
import Map.Constants;
import Map.Mindmap;
import Map.Node;

/**
 * Generates a graphical user interface for the user. Handles controls.
 *
 * @author Lewis Marhin and John Gray-Spence
 */
public class UserInterface extends Canvas implements CommandListener {

    Midlet myMidlet;
    private Command cmNewNode;
    private Command cmRenameNode;
    private Command cmMoveToRoot;
    private Command cmExit;
    private Command cmGlobalMove;
    private Command cmSaveMap;
    private Command cmLoadMap;
    private Command cmNewMap;
    private static int targetNode = 0;
    private Node cursor;
    private Node[] list;
    private Node root;
    private int cursorMode;
    public Mindmap model = new Mindmap(Constants.DEFAULT_ROOT, getHeight()/2, getWidth()/2);

    /**
     * Initial constructor for UserInterface class.
     *
     * @param mid Midlet instance used to bring the midlet's state into the UserInterface class.
     */
    public UserInterface(Midlet mid) {
        myMidlet = mid;

        // Adds functionality to the soft keys
        // Soft keys are displayed according to priority,
        // i.e. Priority 1 Exit will display on it's own
        //      while the Priority 2 functions will be grouped in a menu
        cmExit = new Command("Exit", Command.EXIT, 1);
        cmNewNode = new Command("Add child", Command.ITEM, 2);
        cmRenameNode = new Command("Rename Node", Command.ITEM, 3);
        cmMoveToRoot = new Command("Move to root", Command.ITEM, 4);
        cmGlobalMove = new Command("Scroll screen", Command.ITEM, 5);
        cmSaveMap = new Command("Save MM", Command.ITEM, 6);
        cmLoadMap = new Command("Load MM", Command.ITEM, 7);
        cmNewMap = new Command("New MM", Command.ITEM, 7);

        // Adds softkey options to the screen
        this.addCommand(cmExit);
        this.addCommand(cmNewNode);
        this.addCommand(cmRenameNode);
        this.addCommand(cmMoveToRoot);
        this.addCommand(cmGlobalMove);
        this.addCommand(cmSaveMap);
        this.addCommand(cmLoadMap);
        this.addCommand(cmNewMap);

        // Adds a listener for command type callbacks (made by softkey presses)
        this.setCommandListener(this);

        // Set the cursor to the root on startup
        // (or suffer NullPointerExceptions when you push the arrow keys)
        cursor = model.getNodeList()[0];

        // Set the intial mode to select mode
        cursorMode = Constants.SELECT_MODE;
    }

    /**
     * Recursively called function that updates the screen graphics.
     *
     * @param g A graphics object (automatic).
     */
    protected void paint(Graphics g) {

        // Create background canvas
        g.setColor(Constants.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());

        // Get root and nodes
        root = model.getRoot();
        list = model.getNodeList();

        // Draw root
        g.setColor(Constants.BLACK);
        for (int i = 0; i < Constants.ROOT_DEPTH; i++) {
            g.drawRoundRect(root.getXloc(), root.getYloc(), width(root) + i, Constants.NODE_HEIGHT + i, 5, 5);
        }
        g.drawString(root.getDesc(), root.getXloc() + 3, root.getYloc() + 3, Constants.ANCHOR);

        //Draw connectors
        for (int i = 1; i < model.getNodeCount(); i++) {
            Node parent = list[i].getParent();
            g.drawLine(list[i].getXloc(), list[i].getYloc(), parent.getXloc(), parent.getYloc());
        }

        // Draw nodes (to prevent connectors from showing through, need to draw
        //              a filled rectangle of same width and height)
        for (int i = 1; i < model.getNodeCount(); i++) {
            // Draw inside of node
            g.setColor(Constants.WHITE);
            g.fillRoundRect(list[i].getXloc(), list[i].getYloc(), width(list[i]), Constants.NODE_HEIGHT, Constants.RANGLE, Constants.RANGLE);

            // Draw node outline
            g.setColor(Constants.BLACK);
            g.drawRoundRect(list[i].getXloc(), list[i].getYloc(), width(list[i]), Constants.NODE_HEIGHT, Constants.RANGLE, Constants.RANGLE);

            // Draw node text
            g.drawString(list[i].getDesc(), list[i].getXloc() + 3, list[i].getYloc() + 3, Constants.ANCHOR);
        }

        // Draw selection box around cursor
        if (cursorMode != Constants.MOVE_MODE) {
            g.setColor(Constants.RED);    // NEED TO CHANGE THIS TO A LOOP OR SOMETHING
            g.drawRoundRect(cursor.getXloc(), cursor.getYloc(), width(cursor), Constants.NODE_HEIGHT + 3, Constants.RANGLE, Constants.RANGLE);
            g.drawRoundRect(cursor.getXloc() + 1, cursor.getYloc() + 1, width(cursor), Constants.NODE_HEIGHT + 3, Constants.RANGLE, Constants.RANGLE);
        }

        // Draw movement cursor if node is in move mode
        if (cursorMode == Constants.MOVE_MODE) {
            g.setColor(Constants.BLACK);
            g.fillTriangle(cursor.getXloc(), cursor.getYloc(),
                    cursor.getXloc() + 4, cursor.getYloc() - 8,
                    cursor.getXloc() + 8, cursor.getYloc());
            g.fillTriangle(cursor.getXloc(), cursor.getYloc(),
                    cursor.getXloc() + 8, cursor.getYloc(),
                    cursor.getXloc() + 4, cursor.getYloc() + 8);
            g.fillTriangle(cursor.getXloc() + 4, cursor.getYloc() + 4,
                    cursor.getXloc() + 12, cursor.getYloc(),
                    cursor.getXloc() + 4, cursor.getYloc() - 4);
            g.fillTriangle(cursor.getXloc() + 4, cursor.getYloc() + 4,
                    cursor.getXloc() + 4, cursor.getYloc() - 4,
                    cursor.getXloc() - 4, cursor.getYloc());
        }

        // Draw global movement cursor if node is in global move mode
        if (cursorMode == Constants.GLOBAL_MOVE_MODE) {
            g.setColor(Constants.BLACK);
            g.fillTriangle(getWidth() / 2, getHeight() / 2,
                    getWidth() / 2 + 12, getHeight() / 2 - 24,
                    getWidth() / 2 + 24, getHeight() / 2 + 2);
            g.fillTriangle(getWidth() / 2, getHeight() / 2,
                    getWidth() / 2 + 24, getHeight() / 2,
                    getWidth() / 2 + 12, getHeight() / 2 + 24);
            g.fillTriangle(getWidth() / 2 + 12, getHeight() / 2 + 12,
                    getWidth() / 2 + 36, getHeight() / 2,
                    getWidth() / 2 + 12, getHeight() / 2 - 12);
            g.fillTriangle(getWidth() / 2 + 12, getHeight() / 2 + 12,
                    getWidth() / 2 + 12, getHeight() / 2 -12 ,
                    getWidth() / 2 - 12, getHeight() / 2);

        }
    }

    /**
     * Sets the length of the node based on number of characters in it's label.
     *
     * @param n Node to adjust.
     * @return Length of the node.
     */
    private int width(Node n) {
        return (n.getDesc().length() + 5) * 4;
    }

    /**
     * Catches softkey press events.
     *
     * @param c Event to catch.
     * @param s UNKOWN.
     */
    public void commandAction(Command c, Displayable s) {
        if (c == cmNewNode) {
            cursorMode = Constants.MOVE_MODE; // Return current node to default state before creating new node.
            cursor = model.newNode(cursor); // Create the new node based on the current nodes location.
            this.repaint(); // Update the screen with the new node.
        }
        if (c == cmRenameNode) {
            myMidlet.getDisplay().setCurrent(myMidlet.getRename());
        }
        if (c == cmMoveToRoot) {
            cursorMode = Constants.SELECT_MODE; // Return current node to default state before creating new node.
            cursor = model.getNodeList()[0]; // Retrieve the root node.
            this.repaint(); // Update the screen with new cursor position.
        }
        if (c == cmGlobalMove) {
            cursorMode = Constants.GLOBAL_MOVE_MODE;
        }
        if (c == cmExit) {
            System.exit(1);
        }
        if (c == cmSaveMap) {
            myMidlet.getDisplay().setCurrent(myMidlet.getSave());
        }
        if( c == cmLoadMap) {
            myMidlet.getDisplay().setCurrent(myMidlet.getLoad());
        }
        if (c == cmNewMap) {
            cursor = null;
            model = new Mindmap(Constants.DEFAULT_ROOT, getHeight() / 2, getWidth() / 2);
            cursor = model.getRoot();
        }
    }

    /**
     * Called whenever there is a need for a node's text to be changed.
     * It is called from a form command.
     *
     * @param str New text description for the current node.
     */
    public void setCursorDesc(String str) {
        if (!cursor.getDesc().equals(str)) {
            cursor.setDesc(str);
        }
    }

    /**
     * Returns the description of the current node.
     *
     * @return A string containing the current node's description.
     */
    public String getCursorDesc() {
        return cursor.getDesc();
    }

    /**
     * Initiates file save by getting information from the fmSave form.
     *
     * @param fn Name of the file to be saved.
     * @param loc Determines where the mind map is to be sent/saved (handled by form).
     */
    public void saveFile(String fn, int loc) {
        if (loc == Constants.RMS_LOC) {
            FileOps.saveRMS(model);
        } else if (loc == Constants.FILE_LOC) {
            FileOps.saveFile(list, fn);
        } else if (loc == Constants.SERVER_LOC) {
            FileOps.saveFileServer(list, fn);
        }
    }

    /**
     * Loads a mind map from a file/RMS/server.
     *
     * @param fn The name of the file to be opened.
     * @param loc The location of the file.
     */
    public void loadFile(String fn, int loc) {
        if (loc == Constants.RMS_LOC) {
            FileOps.loadRMS();
        }
    }

    /**
     * Catches key presses (not softkey).
     *
     * @param code Number that is generated by each key.
     */
    protected void keyPressed(int code) {
        int gCode = getGameAction(code);

        if (gCode == Canvas.FIRE) {
            cycleMode(); // Changes current mode to next mode
        } else if (cursorMode == Constants.SELECT_MODE) {
            keyPressSelect(gCode);
        } else if (cursorMode == Constants.MOVE_MODE) {
            keyPressMoving(gCode);
        } else if (cursorMode == Constants.GLOBAL_MOVE_MODE) {
            keyPressGMoving(gCode);
        }

        this.repaint();
    }

    /**
     * Cycles the program mode (select, move, gmove).
     */
    private void cycleMode() {
        if (cursorMode + 1 > Constants.GLOBAL_MOVE_MODE) {
            cursorMode = Constants.SELECT_MODE;
        } else {
            cursorMode++;
        }
    }

    /**
     * Handles event that occurs when key is held down.
     *
     * @param code Number that is generated by each key.
     */
    protected void keyRepeated(int code) {
        keyPressed(code);
    }

    /**
     * Event handling for key presses while in select mode.
     *
     * @param code Integer value of the key press.
     */
    private void keyPressSelect(int code) {
        switch (code) {
            case Canvas.LEFT:
                if (targetNode > 0) {
                    targetNode--;
                } else {
                    targetNode = model.getNodeCount() - 1;
                }

                cursor = model.getNodeList()[targetNode];
                break;
            case Canvas.RIGHT:
                if (targetNode < model.getNodeCount() - 1) {
                    targetNode++;
                } else {
                    targetNode = 0;
                }

                cursor = model.getNodeList()[targetNode];
                break;
            case Canvas.UP:
                if (targetNode > 0) {
                    targetNode--;
                } else {
                    targetNode = model.getNodeCount() - 1;
                }

                cursor = model.getNodeList()[targetNode];
                break;
            case Canvas.DOWN:
                if (targetNode < model.getNodeCount() - 1) {
                    targetNode++;
                } else {
                    targetNode = 0;
                }

                cursor = model.getNodeList()[targetNode];
                break;
        }
    }

    /**
     * Event handling for key presses while in move mode.
     *
     * @param code Integer value of the key press.
     */
    private void keyPressMoving(int code) {
        switch (code) {
            case Canvas.LEFT:
                cursor.setXloc(cursor.getXloc() - 10);
                break;
            case Canvas.RIGHT:
                cursor.setXloc(cursor.getXloc() + 10);
                break;
            case Canvas.UP:
                cursor.setYloc(cursor.getYloc() - 10);
                break;
            case Canvas.DOWN:
                cursor.setYloc(cursor.getYloc() + 10);
                break;
        }
    }

    /**
     * Event handling for key presses while in global move mode.
     *
     * @param code Integer value of the key press.
     */
    private void keyPressGMoving(int code) {
        switch (code) {
            case Canvas.LEFT:
                for (int i = 0; i < model.getNodeCount(); i++) {
                    cursor = model.getNodeList()[i];
                    cursor.setXloc(cursor.getXloc() + 10);
                }
                break;
            case Canvas.RIGHT:
                for (int i = 0; i < model.getNodeCount(); i++) {
                    cursor = model.getNodeList()[i];
                    cursor.setXloc(cursor.getXloc() - 10);
                }
                break;
            case Canvas.UP:
                for (int i = 0; i < model.getNodeCount(); i++) {
                    cursor = model.getNodeList()[i];
                    cursor.setYloc(cursor.getYloc() + 10);
                }
                break;
            case Canvas.DOWN:
                for (int i = 0; i < model.getNodeCount(); i++) {
                    cursor = model.getNodeList()[i];
                    cursor.setYloc(cursor.getYloc() - 10);
                }
                break;
        }
    }
}
